<?php

namespace common\db;

use Yii;
use yii\web\NotFoundHttpException;
use yii\behaviors\TimestampBehavior;
use yii\helpers\ArrayHelper;
use yii\db\Expression;
use common\base\ModelHttpException;

/**
 * ActiveRecord is the base class for classes representing relational data in terms of objects.
 *
 * @author emhome<emhome@163.com>
 * @since 2.0
 */
class ActiveRecord extends \yii\db\ActiveRecord {

    use \common\traits\ModelErrors;
    use \common\traits\DataPierces;
    use \common\traits\SearchForBuildQuery;

    /**
     * {@inheritdoc}
     */
    public function behaviors() {
        $timestampBehavior = [
            'class' => TimestampBehavior::className(),
        ];
        if (!$this->hasAttribute('created_at')) {
            $timestampBehavior['createdAtAttribute'] = false;
        }
        if (!$this->hasAttribute('updated_at')) {
            $timestampBehavior['updatedAtAttribute'] = false;
        }
        return [
            $timestampBehavior,
        ];
    }

    /**
     * {@inheritdoc}
     * @return ActiveQuery the newly created [[ActiveQuery]] instance.
     */
    public static function find() {
        return Yii::createObject(ActiveQuery::class, [get_called_class()]);
    }

    /**
     * 调用存储过程
     *
     * @param $name
     * @param array $params
     * @return \yii\db\Command
     * @throws \yii\db\Exception
     */
    public static function callProcedure($name, $params = []) {
        $db = static::getDb();
        $sql = "CALL $name";
        $tempKeys = [];
        $inParams = [];
        $outFileds = [];
        if (!empty($params)) {
            foreach ($params as $key => $vls) {
                if (is_int($key)) {
                    $outkey = '@' . ltrim($vls, '@');
                    $tempKeys[] = $outkey;
                    $outFileds[] = $outkey;
                } elseif (strpos($key, '@') === 0) {
                    $tempKeys[] = $key;
                    $outFileds[] = $key;
                } elseif (strpos($key, ':') === 0) {
                    $tempKeys[] = $key;
                    $inParams[$key] = $vls;
                } else {
                    $tempKeys[] = ':' . $key;
                    $inParams[':' . $key] = $vls;
                }
            }
        }
        if (!empty($tempKeys)) {
            $sql .= '(' . implode(',', $tempKeys) . ')';
        }
        $sql .= ';';
        $query = $db->createCommand($sql)->bindValues($inParams);
        if (empty($outFileds)) {
            return $query;
        }
        $query->execute();
        return $db->createCommand('SELECT ' . implode(',', $outFileds) . ';');
    }

    /**
     * 加入记录软删除
     * {@inheritdoc}
     *
     * @param bool $hard
     * @return bool|false|int
     * @throws \yii\db\StaleObjectException
     */
    public function delete($hard = false) {
        if ($hard == false && $this->hasAttribute('deleted_at')) {
            $this->setAttribute('deleted_at', time());
            return $this->save();
        }
        return parent::delete();
    }

    /**
     * 查询对象是否存在存在返回记录不存在创建
     *
     * @param mixed $condition please refer to [[findOne()]] for the explanation of this parameter
     * @return static
     */
    public static function findOrNew($condition) {
        $model = static::findOne($condition);
        if ($model === null) {
            $model = static::instance();
            if (is_array($condition)) {
                $model->setAttributes($condition);
            }
        }
        return $model;
    }

    /**
     * 查询对象是否存在存在返回记录不存在创建
     *
     * @param mixed $condition please refer to [[findOne()]] for the explanation of this parameter
     * @return static
     * @throws ModelHttpException
     */
    public static function findOrCreate($condition) {
        $model = static::findOne($condition);
        if ($model === null) {
            $model = static::instance();
            if (is_array($condition)) {
                $model->setAttributes($condition);
            }
            if (!$model->save()) {
                throw new ModelHttpException('数据更新异常', $model->getErrorMsg());
            }
        }
        return $model;
    }

    /**
     * 查询对象是否存在存在返回记录不存在返回异常
     *
     * @param mixed $condition please refer to [[findOne()]] for the explanation of this parameter
     * @return static
     * @throws NotFoundHttpException
     */
    public static function findOrFail($condition) {
        $model = static::findOne($condition);
        if ($model === null) {
            throw new NotFoundHttpException('未找到数据模型对应的记录！');
        }
        return $model;
    }

    /**
     * {@inheritdoc}
     * @param null $alias
     * @param bool $state
     * @return ActiveQuery the newly created [[ActiveQuery]] instance.
     * @throws \yii\base\InvalidConfigException
     */
    public static function findActive($alias = null, $state = false) {
        $query = static::find();
        if ($alias) {
            $query->alias($alias);
        }
        $query->active($state);
        return $query;
    }

    /**
     * @param $class
     * @param $link
     * @param $pk
     * @return ActiveQuery|\yii\db\ActiveQuery
     */
    public function hasOneX($class, $link, $pk) {
        /* @var $class SequenceActiveRecord */
        /* @var $query ActiveQuery */
        static::log($pk);
        $query = $class::findx($pk);
        $query->primaryModel = $this;
        $query->link = $link;
        $query->multiple = false;
        return $query;
    }

    /**
     * @param $class
     * @param $link
     * @param $pk
     * @return ActiveQuery
     */
    public function hasManyX($class, $link, $pk) {
        /* @var $class SequenceActiveRecord */
        /* @var $query ActiveQuery */
        $query = $class::findx($pk);
        $query->primaryModel = $this;
        $query->link = $link;
        $query->multiple = true;
        return $query;
    }

    /**
     * Sets the named attribute value.
     *
     * @param string $name the attribute name
     * @param mixed $value the attribute value.
     * @throws InvalidArgumentException if the named attribute does not exist.
     * @see hasAttribute()
     */
    public function setGeometryAttribute($name, $value) {
        if (is_array($value) && count($value) == 2) {
            list($lng, $lat) = $value;
            //$value = new Expression("GEOMFROMTEXT('POINT($lng $lat)')");
            $value = new Expression("POINT($lng,$lat)");
        }
        $this->setAttribute($name, $value);
    }

    /**
     * {@inheritdoc}
     * @return ActiveQuery the newly created [[ActiveQuery]] instance.
     */
    public function afterSave($insert, $changedAttributes) {
        if (YII_DEBUG) {
            Yii::error([
                $this->tableName(),
                $this->className(),
                $insert,
                $changedAttributes,
            ], __METHOD__);
        }
        parent::afterSave($insert, $changedAttributes);
    }

    /**
     * 记录AR日志
     *
     * @param null $content
     */
    public static function log($content = null) {
        if (!YII_DEBUG) {
            return;
        }
        if ($content instanceof ActiveQuery) {
            Yii::error($content->debug(), __METHOD__);
        } else {
            Yii::error($content, __METHOD__);
        }
    }

    /**
     * @return array
     */
    public function options() {
        $fields = $this->fields();
        if (empty($fields)) {
            $fields = array_keys($this->attributes);
        }
        $options = [];
        $configs = $this->attributeConfigs();
        foreach ($fields as $attribute) {
            $_config = ArrayHelper::getValue($configs, $attribute, []);
            $options[] = ArrayHelper::merge([
                'label' => $this->getAttributeLabel($attribute),
                'prop' => $attribute,
            ], $_config);
        }
        return $options;
    }

    /**
     * @return array
     */
    public function attributeConfigs() {
        return [];
    }

    /**
     * Returns attribute values.
     *
     * @param array|null $names list of attributes whose value needs to be returned.
     * Defaults to null, meaning all attributes listed in [[attributes()]] will be returned.
     * If it is an array, only the attributes in the array will be returned.
     * @param array $except list of attributes whose value should NOT be returned.
     * @return array attribute values (name => value).
     * @return array
     */
    public function getCamelAttributes($names = null, $except = array()) {
        $values = [];
        if ($names === null) {
            $names = $this->attributes();
        }
        foreach ($names as $name) {
            $key = Inflector::variablize($name, '_');
            $values[$key] = $this->$name;
        }
        foreach ($except as $name) {
            $key = Inflector::variablize($name, '_');
            unset($values[$key]);
        }
        return $values;
    }

    /**
     * @param $attribute
     * @return string
     */
    protected function buildCachePrefix($attribute) {
        $primaryKey = $this->primaryKey;
        $cacheName = array_filter([
            static::class,
            $attribute,
            $primaryKey,
        ]);
        return implode('_', $cacheName);
    }

    /**
     * @param null $attribute
     * @param int $duration
     * @return array|mixed|null
     */
    public function getCacheAttributes($attribute = null, $duration = 1800) {
        $cache = Yii::$app->cache;
        $cacheName = $this->buildCachePrefix($attribute);
        $data = $cache->get($cacheName);
        if ($data !== false) {
            return $data;
        }
        if ($attribute === null) {
            $data = $this->attributes;
        } else {
            $method = 'get' . ucfirst($attribute);
            $handler = array($this, $method);
            if ($this->hasMethod($method) && is_callable($handler)) {
                $data = call_user_func_array($handler, [$this]);
            } elseif ($this->hasAttribute($attribute)) {
                $data = $this->getAttribute($attribute);
            }
        }
        if ($data !== false) {
            $cache->set($cacheName, $data, $duration);
        }
        return $data;
    }

}
